﻿
using System.Threading.Tasks;
#if NET6_0_OR_GREATER
using System.Collections.Immutable;
#endif

namespace zijian666.AnyExtensions;

/// <summary>
/// 数据聚合构建处理扩展方法
/// </summary>
public static class DataAggregateBuilderExtensions
{
    /// <summary>
    /// 数据流转结构
    /// </summary>
    public readonly record struct FlowData<TEntity, TValue>
    {
        public FlowData(TEntity entity, TValue value)
        {
            Entity = entity;
            Value = value;
        }
        public readonly TEntity Entity;
        public readonly TValue Value;
    }

    /// <summary>
    /// 异步 <see cref="Func{T, TResult}"/> 委托
    /// </summary>
    public delegate Task<TResult> FuncAsync<T, TResult>(T arg);

    /// <summary>
    /// 异步 <see cref="Action{T}"/> 委托
    /// </summary>
    public delegate Task ActionAsync<in T>(T obj);

    #region   Sync -> Sync -> Sync

    /// <summary>
    /// 使用指定的选择器函数对数据进行聚合，生成包含原始数据和选择器结果的<see cref="FlowData{TEntity, TValue}"/>序列。
    /// </summary>
    /// <typeparam name="TEntity">输入数据的类型。</typeparam>
    /// <typeparam name="TValue">选择器函数返回值的类型。</typeparam>
    /// <param name="datas">要聚合的数据集合。</param>
    /// <param name="selector">用于从每个数据项中提取值的函数。</param>
    /// <returns>一个<see cref="IEnumerable{FlowData{TEntity, TValue}}"/>，其中每个元素都是由原始数据和选择器函数的结果组成的<see cref="FlowData{TEntity, TValue}"/>实例。</returns>
    public static IEnumerable<FlowData<TEntity, TValue>> Aggregate<TEntity, TValue>(this IEnumerable<TEntity> datas, Func<TEntity, TValue> selector)
    {
        foreach (var data in datas)
        {
            yield return new(data, selector(data));
        }
    }
    
    /// <summary>
    /// 对给定的实体集合应用选择器函数，将每个实体映射到多个值，并返回一个新的 FlowData 集合。
    /// </summary>
    /// <typeparam name="TEntity">实体类型。</typeparam>
    /// <typeparam name="TValue">选择器函数返回的值的类型。</typeparam>
    /// <param name="datas">要处理的实体集合。</param>
    /// <param name="selector">一个函数，用于从每个实体中选择多个值。</param>
    /// <returns>包含 FlowData 实例的序列。</returns>
    public static IEnumerable<FlowData<TEntity, TValue>> AggregateMany<TEntity, TValue>(this IEnumerable<TEntity> datas, Func<TEntity, IEnumerable<TValue>> selector)
    {
        foreach (var data in datas)
        {
            foreach (var item in selector(data))
            {
                yield return new(data, item);
            }
        }
    }

    /// <summary>
    /// 使用给定的谓词函数过滤 FlowData 集合。
    /// </summary>
    /// <typeparam name="TEntity">实体类型。</typeparam>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="datas">要过滤的 FlowData 集合。</param>
    /// <param name="predicate">用于过滤的谓词函数。</param>
    /// <returns>过滤后的 FlowData 集合。</returns>
    public static IEnumerable<FlowData<TEntity, TValue>> Filter<TEntity, TValue>(this IEnumerable<FlowData<TEntity, TValue>> datas, Func<TValue, bool> predicate)
    {
        foreach (var data in datas)
        {
            if (predicate(data.Value))
            {
                yield return data;
            }
        }
    }

    /// <summary>
    /// 从数据流中筛选出值不为 null 的元素。
    /// </summary>
    /// <typeparam name="TEntity">实体类型。</typeparam>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="datas">包含可空值的数据流。</param>
    /// <returns>一个只包含非空值的 <see cref="IEnumerable{FlowData{TEntity, TValue}}"/>。</returns>
    public static IEnumerable<FlowData<TEntity, TValue>> Notnull<TEntity, TValue>(this IEnumerable<FlowData<TEntity, TValue?>> datas)
    {
        foreach (var data in datas)
        {
            if (data.Value is not null)
            {
                yield return new(data.Entity, data.Value);
            }
        }
    }
   
    /// <summary>
    /// 从给定的数据集合中构建新的数据流，使用指定的输出函数转换旧值为新值。
    /// </summary>
    /// <typeparam name="TEntity">实体类型。</typeparam>
    /// <typeparam name="TOldValue">旧值类型。</typeparam>
    /// <typeparam name="TNewValue">新值类型。</typeparam>
    /// <param name="datas">包含旧值的数据集合。</param>
    /// <param name="output">用于将旧值数组转换为新值字典的函数。</param>
    /// <returns>返回一个新的数据流，其中每个元素都包含了转换后的新值。</returns>
    public static IEnumerable<FlowData<TEntity, TNewValue?>> Build<TEntity, TOldValue, TNewValue>(this IEnumerable<FlowData<TEntity, TOldValue>> datas, Func<TOldValue[], Dictionary<TOldValue, TNewValue>> output)
    {
        var old = datas.ToImmutableArray();
        var newValues = output(old.Select(x => x.Value).Distinct().ToArray());
        foreach (var data in old)
        {
            newValues.TryGetValue(data.Value, out var newValue);
            yield return new(data.Entity, newValue);
        }
    }

    /// <summary>
    /// 对指定的 FlowData 集合中的每个元素执行给定的操作。
    /// </summary>
    /// <typeparam name="TEntity">FlowData 中实体的类型。</typeparam>
    /// <typeparam name="TValue">FlowData 中值的类型。</typeparam>
    /// <param name="datas">要对其元素执行操作的 FlowData 集合。</param>
    /// <param name="action">要对每个元素执行的操作。</param>
    public static void Action<TEntity, TValue>(this IEnumerable<FlowData<TEntity, TValue>> datas, Action<FlowData<TEntity, TValue>> action)
    {
        foreach (var data in datas)
        {
            action(data);
        }
    }

    /// <summary>
    /// 将指定的 <see cref="IEnumerable{FlowData{TEntity, TValue}}"/> 转换为 <see cref="List{TResult}"/>，使用提供的选择器函数。
    /// </summary>
    /// <typeparam name="TEntity">流数据中的实体类型。</typeparam>
    /// <typeparam name="TValue">流数据中的值类型。</typeparam>
    /// <typeparam name="TResult">转换后的结果类型。</typeparam>
    /// <param name="datas">要转换的 <see cref="IEnumerable{FlowData{TEntity, TValue}}"/>。</param>
    /// <param name="selector">用于将每个 <see cref="FlowData{TEntity, TValue}"/> 映射到 <typeparamref name="TResult"/> 的选择器函数。</param>
    /// <returns>包含转换结果的 <see cref="List{TResult}"/>。</returns>
    public static List<TResult> ToList<TEntity, TValue, TResult>(this IEnumerable<FlowData<TEntity, TValue>> datas, Func<FlowData<TEntity, TValue>, TResult> selector)
    {
        var list = new List<TResult>();
        foreach (var data in datas)
        {
            list.Add(selector(data));
        }
        return list;
    }

    #endregion

    #region   Task -> Sync -> Task

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Aggregate<TEntity, TValue>(this Task<IEnumerable<TEntity>> datas, Func<TEntity, TValue> selector)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in await datas)
        {
            res.Add(new(data, selector(data)));
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> AggregateMany<TEntity, TValue>(this Task<IEnumerable<TEntity>> datas, Func<TEntity, IEnumerable<TValue>> selector)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in await datas)
        {
            foreach (var item in selector(data))
            {
                res.Add(new(data, item));
            }
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Filter<TEntity, TValue>(this Task<IEnumerable<FlowData<TEntity, TValue>>> datas, Func<TValue, bool> predicate)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in await datas)
        {
            if (predicate(data.Value))
            {
                res.Add(data);
            }
        }
        return res;
    }


    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Notnull<TEntity, TValue>(this Task<IEnumerable<FlowData<TEntity, TValue?>>> datas)
    {
        var res = new List<FlowData<TEntity, TValue?>>();
        foreach (var data in await datas)
        {
            if (data.Value is not null)
            {
                res.Add(new(data.Entity, data.Value));
            }
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TNewValue?>>> Build<TEntity, TOldValue, TNewValue>(this Task<IEnumerable<FlowData<TEntity, TOldValue>>> datas, Func<TOldValue[], Dictionary<TOldValue, TNewValue>> output)
    {
        var res = new List<FlowData<TEntity, TNewValue?>>();
        var old = (await datas).ToImmutableArray();
        var newValues = output(old.Select(x => x.Value).Distinct().ToArray());
        foreach (var data in old)
        {
            newValues.TryGetValue(data.Value, out var newValue);
            res.Add(new(data.Entity, newValue));
        }
        return res;
    }

    public static async Task Action<TEntity, TValue>(this Task<IEnumerable<FlowData<TEntity, TValue>>> datas, Action<FlowData<TEntity, TValue>> action)
    {
        foreach (var data in await datas)
        {
            action(data);
        }
    }


    public static async Task<List<TResult>> ToList<TEntity, TValue, TResult>(this Task<IEnumerable<FlowData<TEntity, TValue>>> datas, Func<FlowData<TEntity, TValue>, TResult> selector)
    {
        var res = new List<TResult>();
        foreach (var data in await datas)
        {
            res.Add(selector(data));
        }
        return res;
    }

    #endregion

    #region Sync -> Task -> Task

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Aggregate<TEntity, TValue>(this IEnumerable<TEntity> datas, FuncAsync<TEntity, TValue> selector)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in datas)
        {
            res.Add(new(data, await selector(data)));
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> AggregateMany<TEntity, TValue>(this IEnumerable<TEntity> datas, FuncAsync<TEntity, IEnumerable<TValue>> selector)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in datas)
        {
            foreach (var item in await selector(data))
            {
                res.Add(new(data, item));
            }
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Filter<TEntity, TValue>(this IEnumerable<FlowData<TEntity, TValue>> datas, FuncAsync<TValue, bool> predicate)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in datas)
        {
            if (await predicate(data.Value))
            {
                res.Add(data);
            }
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TNewValue?>>> Build<TEntity, TOldValue, TNewValue>(this IEnumerable<FlowData<TEntity, TOldValue>> datas, FuncAsync<TOldValue[], Dictionary<TOldValue, TNewValue>> output)
    {
        var res = new List<FlowData<TEntity, TNewValue?>>();
        var old = datas.ToImmutableArray();
        var newValues = await output(old.Select(x => x.Value).Distinct().ToArray());
        foreach (var data in old)
        {
            newValues.TryGetValue(data.Value, out var newValue);
            res.Add(new(data.Entity, newValue));
        }
        return res;
    }

    public static async Task Action<TEntity, TValue>(this IEnumerable<FlowData<TEntity, TValue>> datas, ActionAsync<FlowData<TEntity, TValue>> action)
    {
        foreach (var data in datas)
        {
            await action(data);
        }
    }

    public static async Task<List<TResult>> ToList<TEntity, TValue, TResult>(this IEnumerable<FlowData<TEntity, TValue>> datas, FuncAsync<FlowData<TEntity, TValue>, TResult> selector)
    {
        var res = new List<TResult>();
        foreach (var data in datas)
        {
            res.Add(await selector(data));
        }
        return res;
    }

    #endregion

    #region Task -> Task -> Task

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Aggregate<TEntity, TValue>(this Task<IEnumerable<TEntity>> datas, FuncAsync<TEntity, TValue> selector)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in await datas)
        {
            res.Add(new(data, await selector(data)));
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> AggregateMany<TEntity, TValue>(this Task<IEnumerable<TEntity>> datas, FuncAsync<TEntity, IEnumerable<TValue>> selector)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in await datas)
        {
            foreach (var item in await selector(data))
            {
                res.Add(new(data, item));
            }
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TValue>>> Filter<TEntity, TValue>(this Task<IEnumerable<FlowData<TEntity, TValue>>> datas, FuncAsync<TValue, bool> predicate)
    {
        var res = new List<FlowData<TEntity, TValue>>();
        foreach (var data in await datas)
        {
            if (await predicate(data.Value))
            {
                res.Add(data);
            }
        }
        return res;
    }

    public static async Task<IEnumerable<FlowData<TEntity, TNewValue?>>> Build<TEntity, TOldValue, TNewValue>(this Task<IEnumerable<FlowData<TEntity, TOldValue>>> datas, FuncAsync<TOldValue[], Dictionary<TOldValue, TNewValue>> output)
    {
        var res = new List<FlowData<TEntity, TNewValue?>>();
        var old = (await datas).ToImmutableArray();
        var newValues = await output(old.Select(x => x.Value).Distinct().ToArray());
        foreach (var data in old)
        {
            newValues.TryGetValue(data.Value, out var newValue);
            res.Add(new(data.Entity, newValue));
        }
        return res;
    }

    public static async Task Action<TEntity, TValue>(this Task<IEnumerable<FlowData<TEntity, TValue>>> datas, ActionAsync<FlowData<TEntity, TValue>> action)
    {
        foreach (var data in await datas)
        {
            await action(data);
        }
    }

    public static async Task<List<TResult>> ToList<TEntity, TValue, TResult>(this Task<IEnumerable<FlowData<TEntity, TValue>>> datas, FuncAsync<FlowData<TEntity, TValue>, TResult> selector)
    {
        var res = new List<TResult>();
        foreach (var data in await datas)
        {
            res.Add(await selector(data));
        }
        return res;
    }

    #endregion

#if NET6_0_OR_GREATER

#else
    private static T[] ToImmutableArray<T>(this IEnumerable<T> datas) { return datas.ToArray(); }
#endif
}
